/*
 *  @ProductName@
 *  Copyright (C) @CopyrightYear@
 *    National Institute of Advanced Industrial Science and Technology (AIST)
 *    Registration Number: @AISTRegistrationNumber@
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package jp.go.aist.six.vuln.core.scap.nvd;

import jp.go.aist.six.util.IsoDate;
import jp.go.aist.six.vuln.model.scap.nvd.Nvd;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.List;




/**
 * A data installer for the NVD/CVE XML Feed.
 *
 * @author  Akihito Nakamura, AIST
 * @version $Id: NvdXmlFeedInstaller.java 256 2010-06-14 03:58:05Z nakamura5akihito $
 */
public class NvdXmlFeedInstaller
{

    public static void main(
                    final String[] args
                    )
    {
        NvdXmlFeedInstaller  installer = new NvdXmlFeedInstaller();
        installer.execute( args );
    }



    private static final String[]  _START_MESSAGE_
    = new String[] {
            "NVD XML Feed Installer, SIX VULN, version 0.9.0",
            "copyright (C) 2006-2010 Akihito Nakamura",
            "AIST Registered Number H18PRO-538"
    };



    public static final String  RECENT_FEED_TYPE = "recent";
    public static final String  MODIFIED_FEED_TYPE = "modified";
    public static final int  MINIMUM_FEED_YEAR = 2002;

    /**
     */
    private static Collection<String> _createYearlyFeedTypes()
    {
        final int  currentYear = Calendar.getInstance().get( Calendar.YEAR );
        Collection<String>  types = new ArrayList<String>();
        for (int  year = MINIMUM_FEED_YEAR; year <= currentYear; year++ ) {
            types.add( String.valueOf( year ) );
        }

        return Collections.unmodifiableCollection( types );
    }



    /**
     */
    private static Collection<String> _createAllFeedTypes()
    {
        Collection<String>  types = new ArrayList<String>();
        types.addAll( _createYearlyFeedTypes() );
        types.add( RECENT_FEED_TYPE );
        types.add( MODIFIED_FEED_TYPE );

        return Collections.unmodifiableCollection( types );
    }


    private static Collection<String>  _ALL_FEED_TYPES_ = _createAllFeedTypes();



    public static final String  XML_FEED_URL_PATTERN
    = "http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-{0}.xml";

    /**
     * Returns the URL of an NVD XML Feed of the specified type.
     * The type is one of: "recent", "modified", "2002", "2003",...
     *
     * @param   type
     *  the XML Feed type.
     * @return
     *  the URL.
     */
    public static String getXmlFeedUrl(
                    final String type
                    )
    {
        if (_ALL_FEED_TYPES_.contains( type )) {
            // OK
        } else {
            throw new IllegalArgumentException( "unknown XML Feed type: " + type );
        }

        return MessageFormat.format( XML_FEED_URL_PATTERN, type );
    }



    private static final PrintStream  _STDOUT_ = System.out;

    /**
     */
    private static void _printMessage(
                    final String message
                    )
    {
        _STDOUT_.println( message );
    }



    /**
     * Constructor.
     */
    public NvdXmlFeedInstaller()
    {
    }



    /**
     * Executes the installer.
     *
     * @param   args
     *  the command line arguments.
     * @throws Exception
     */
    public void execute(
                    final String[] args
                    )
    {
        _printStartMessage( _START_MESSAGE_ );

        List<String>  locations = null;
        if (args.length == 0) {
            locations = _getAllYearlyXmlFeedLocations();
        } else {
            locations = _getXmlFeedLocations( args );
        }

        _printXmlFeedLocations( locations );

        for (String  location : locations) {
            _printMessage( "*** install: " + location );
            try {
                _install( location );
            } catch (Exception ex) {
                _printMessage( "@@@ ERROR: " + location
                                + " - " + ex.getMessage() );
                _printMessage( "...skip" );
            }
        }
    }



    /**
     * Prints some message at the start.
     */
    private void _printStartMessage(
                    final String[] lines
                    )
    {
        _printMessage( "--------------------------------" );
        for (String  line : lines) {
            _printMessage( line);
        }
        _printMessage( "--------------------------------\n" );
    }



    /**
     */
    private void _printXmlFeedLocations(
                    final List<String> locations
                    )
    {
        _printMessage( "*** XML Feed to install: " );
        for (String  location : locations) {
            _printMessage( "  - " + location );
        }
        _printMessage( "\n" );
    }



    /**
     * Installs the XML Feed to the data store.
     * The location of the Feed is a file path or URL.
     *
     * @param   location
     *  the location of the XML Feed.
     * @return
     *  the persistent ID of the installed Nvd object.
     */
    private Object _install(
                    final String location
                    )
    throws Exception
    {
        if (location.startsWith( "http://" )
                        || location.startsWith( "https://" )
                        || location.startsWith( "file:/" )) {
            return _install( new URL( location ) );
        } else {
            File  file = new File( location );
            return _install( file );
        }
    }



    /**
     * Installs the XML Feed to the data store.
     *
     * @param   file
     *  the file containing the XML Feed.
     * @return
     *  the persistent ID of the installed Nvd object.
     */
    private Object _install(
                    final File file
                    )
    throws Exception
    {
        return _install( new FileInputStream( file ) );
    }



    /**
     * Installs the XML Feed to the data store.
     *
     * @param   url
     *  the URL of the XML Feed.
     * @return
     *  the persistent ID of the installed Nvd object.
     */
    private Object _install(
                    final URL url
                    )
    throws Exception
    {
        return _install( url.openStream() );
    }



    /**
     * Installs the XML Feed to the data store.
     *
     * @param   stream
     *  the stream which the XML Feed is read from.
     * @return
     *  the persistent ID of the installed Nvd object.
     */
    private Object _install(
                    final InputStream stream
                    )
    throws Exception
    {
        NvdService  service = new NvdService();
        _printMessage( "unmarshalling XML Feed..." );
        Object  obj = service.getXml().unmarshal( stream );
        _printMessage( "...done" );

        Nvd  nvd = Nvd.class.cast( obj );
        String  pubDate = IsoDate.format( nvd.getPubDate() );
        _printMessage( "  - pub_date=" +  pubDate );
        _printMessage( "  - nvd_xml_version=" + nvd.getNvdXmlVersion() );
        _printMessage( "  - #entries=" + nvd.getEntry().size() );

        _printMessage( "storing objects..." );
        Object  pid = service.getStore().createNvd( nvd );
        _printMessage( "...done" );
        _printMessage( "  - PID=" + pid + "\n" );

        return pid;
    }



    /**
     * Returns the URLs of all the yearly Feeds; from 2002 to this year.
     */
    private List<String> _getAllYearlyXmlFeedLocations()
    {
        List<String>  locations = new ArrayList<String>();

        for (String  year : _createYearlyFeedTypes()) {
            String  url = getXmlFeedUrl( year );
            locations.add( url );
        }

        return locations;
    }



    /**
     * Returns the URLs of the XML Feeds specified by the command line arguments.
     */
    private List<String> _getXmlFeedLocations(
                    final String[] args
                    )
    {
        List<String>  locations = new ArrayList<String>();

        for (String  arg : args) {
            if (arg.startsWith( "-" )) {
                String  type = arg.substring( 1 );
                String  url = getXmlFeedUrl( type );
                locations.add( url );
            } else {
                File  file = new File( arg );
                if (file.exists()) {
                    if (file.isFile()) {
                        _validateXmlFile( file );
                        locations.add( arg );
                    } else if (file.isDirectory()) {
                        _collectXmlFiles( locations, file );
                    } else {
                        throw new IllegalArgumentException(
                                        "unsupported XML Feed file: " + arg );
                    }
                } else {
                    // Regard as URL.
                    try {
                        // URL validation
                        new URL( arg );
                        locations.add( arg );
                    } catch (MalformedURLException ex) {
                        throw new IllegalArgumentException(
                                        "malformed XML Feed URL: " + arg );
                    }
                }
            }
        }

        return locations;
    }



    /**
     * Validates the specified XML Feed file.
     * The file is valid if it exists, is a normal file, and can be read.
     */
    private void _validateXmlFile(
                    final File file
                    )
    {
        if (file.exists()  &&  file.isFile()  &&  file.canRead()) {
            // OK
        } else {
            throw new IllegalArgumentException(
                            "XML Feed file inexistent or read-protected: "
                            + file.getAbsolutePath() );
        }
    }



    /**
     */
    private void _collectXmlFiles(
                    final List<String> locations,
                    final File dir
                    )
    {
        _printMessage( "*** collectiong XML Feed files in directory: "
                        + dir.getAbsolutePath() );

        File[]  files = dir.listFiles( new XmlFilenameFilter() );
        for (File  file : files) {
            String  filepath = file.getAbsolutePath();
            try {
                _validateXmlFile( file );
                locations.add( filepath );
                _printMessage( "  - file collected: " + filepath );
            } catch (Exception ex) {
                _printMessage( "@@@ WARNING: " + filepath
                                + " - " + ex.getMessage() );
                _printMessage( "...skip" );
            }
        }
    }



    /**
     * A file name filter for XML files.
     * The XML files are identified using the ".xml" extension.
     */
    private static class XmlFilenameFilter
    implements FilenameFilter
    {

        public boolean accept(
                        final File dir,
                        final String name
                        )
        {
            return (name.endsWith( ".xml" )  ||  name.endsWith( ".XML" ));
        }

    }
    // XmlFilenameFilter

}
// NvdInstaller

/* vim:set tabstop=4:set expandtab:set shiftwidth=4: */

